package com.llt.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.llt.common.core.constants.Constants;
import com.llt.common.core.domain.R;
import com.llt.common.core.domain.TableDataInfo;
import com.llt.common.core.enums.ResultCode;
import com.llt.common.security.exception.ServiceException;
import com.llt.system.domain.exam.dto.ExamAddDTO;
import com.llt.system.domain.exam.dto.ExamEditDTO;
import com.llt.system.domain.exam.dto.ExamQueryDTO;
import com.llt.system.domain.exam.dto.ExamQuestionAddDTO;
import com.llt.system.domain.exam.po.Exam;
import com.llt.system.domain.exam.po.ExamQuestion;
import com.llt.system.domain.exam.vo.ExamDetailVO;
import com.llt.system.domain.exam.vo.ExamVO;
import com.llt.system.domain.question.po.Question;
import com.llt.system.domain.question.vo.QuestionVO;
import com.llt.system.manager.ExamCacheManager;
import com.llt.system.mapper.exam.ExamMapper;
import com.llt.system.mapper.exam.ExamQuestionMapper;
import com.llt.system.mapper.question.QuestionMapper;
import com.llt.system.service.exam.IExamService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author llt
 * @since 2024-10-08
 */
@Service
@RequiredArgsConstructor
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements IExamService {

    private final ExamMapper examMapper;

    private final QuestionMapper questionMapper;

    private final ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Override
    public TableDataInfo list(ExamQueryDTO dto) {
        // 1. 使用 PageHelper 进行分页查询
        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
        List<ExamVO> list = examMapper.selectExamList(dto);
        if (CollectionUtil.isEmpty(list)) {
            return TableDataInfo.empty();
        }

        // 2. 总条数,
        long total = new PageInfo<>(list).getTotal();

        return TableDataInfo.success(list, total);
    }

    @Override
    public R<String> examAdd(ExamAddDTO dto) {
        // 1. 参数校验
        checkExamSaveParams(dto, null);

        // 2. 把 dto 转换为 po
        Exam exam = new Exam();
        BeanUtil.copyProperties(dto, exam);

        // 3. 保存
        boolean save = this.save(exam);

        return save ? R.ok(exam.getExamId().toString()) : R.fail();
    }

    @Override
    public R<Void> questionAdd(ExamQuestionAddDTO dto) {
        // 1. 判断竞赛是否存在
        Exam exam = getExam(dto.getExamId());

        // 2. 判断竞赛是否有问题
        Set<Long> questionIdSet = dto.getQuestionIdSet();
        if (questionIdSet == null) {
            // 没有添加问题
            return R.ok();
        }

        // 3. 判断是否有这些问题
        List<Question> questions = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isEmpty(questions) || questions.size() < questionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        // 5. 插入到竞赛问题表
        insertExamQuestion(questionIdSet, exam);

        return R.ok();
    }

    @Override
    public R<ExamDetailVO> detail(Long examId) {
        ExamDetailVO vo = new ExamDetailVO();
        Exam exam = getExam(examId);
        BeanUtil.copyProperties(exam, vo);

        // 1. 拿到竞赛的信息
        List<ExamQuestion> eqList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if (CollectionUtil.isEmpty(eqList)) {
            return R.ok(vo);
        }

        // 2. 题目id
        List<Long> qIdList = eqList.stream().map(ExamQuestion::getQuestionId).toList();

        // 3. 拿到题目信息
        List<Question> questions = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, qIdList));
        List<QuestionVO> questionVOList = BeanUtil.copyToList(questions, QuestionVO.class);

        // 4. 存入 vo 返回
        vo.setExamQuestionList(questionVOList);

        return R.ok(vo);
    }

    @Override
    public R<Void> deleteExam(Long examId) {
        // 1. 判断是否已发布
        Exam exam = getExam(examId);
        if (exam.getStatus() == 1) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        // 2. 判断竞赛时间是否在当前时间之前
        checkExam(exam);

        // 3. 删除竞赛问题表中的数据
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));

        // 4. 删除竞赛表中的数据
        boolean isDelete = this.removeById(examId);

        return isDelete ? R.ok() : R.fail();
    }

    @Override
    public R<Integer> questionDelete(Long examId, Long questionId) {
        // 1. 判断竞赛是否存在
        Exam exam = getExam(examId);

        // 2. 判断是否能删
        checkExam(exam);

        // 3. 删除竞赛问题表中数据
        int delete = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));

        return R.ok(delete);
    }

    @Override
    public R<Void> publish(Long examId) {
        // 1. 判断竞赛是否存在
        Exam exam = getExam(examId);
        // 竞赛结束不能发布
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        // 2. 判断竞赛中是否存在题目
        Long questionCount = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if (questionCount == null || questionCount <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }

        // 3. 发布竞赛(修改状态 0 -> 1)
        exam.setStatus(Constants.TRUE);

        // 4. 将新发布的竞赛数据存到 redis 中
        examCacheManager.addCache(exam);

        return examMapper.updateById(exam) == 0 ? R.fail() : R.ok();
    }

    @Override
    public R<Void> cancelPublish(Long examId) {
        // 1. 判断竞赛是否存在
        Exam exam = getExam(examId);
        // 竞赛结束不能撤销
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        // 2. 判断竞赛是否已经开始
        checkExam(exam);

        // 3. 撤销竞赛(修改状态 1 -> 0)
        exam.setStatus(Constants.FALSE);

        // 4. 从缓存中移除
        examCacheManager.deleteCache(examId);

        return examMapper.updateById(exam) == 0 ? R.fail() : R.ok();
    }

    @Override
    public R<Integer> edit(ExamEditDTO dto) {
        // 1. 参数校验
        Exam exam = getExam(dto.getExamId());
        checkExamSaveParams(dto, dto.getExamId());

        // 2. 更新
        exam.setTitle(dto.getTitle());
        exam.setStartTime(dto.getStartTime());
        exam.setEndTime(dto.getEndTime());
        return R.ok(examMapper.updateById(exam));
    }

    private void insertExamQuestion(Set<Long> questionIdSet, Exam exam) {
        int num = 1;
        for (Long questionId : questionIdSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setQuestionId(questionId);
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionOrder(num++);
            examQuestionMapper.insert(examQuestion);
        }
    }

    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return exam;
    }

    private void checkExam(Exam exam) {
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    private void checkExamSaveParams(ExamAddDTO dto, Long examId) {
        // 1. 判断标题是否重复
        boolean hasExam = this.lambdaQuery()
                .eq(Exam::getTitle, dto.getTitle())
                .ne(examId != null, Exam::getExamId, examId)
                .exists();
        if(hasExam) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }

        // 2. 判断时间是否符合要求
        if (dto.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
        }
        if (dto.getStartTime().isAfter(dto.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }
}
